package com.woniuxy.gateway.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.woniuxy.commons.request.ResponseResult;
import com.woniuxy.commons.service.FeignAuthService;
import com.woniuxy.gateway.util.RefreshResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;


@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    @Resource
    private FeignAuthService authService;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private WebClient webClient;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getURI().getPath();
        log.info("path:" + path);

        // 判断path是否需要登录
        if (requireAuthentication(path)){
            // 需要登录
            log.info("获取token和refreshtoken");
            List<String> authorizationList = request.getHeaders().get("Authorization");

            // 对token和refresh token进行基础校验
            if (!checkTokenBaseInfo(exchange)){
                return accessDenied(exchange, 403, "请登录");
            }

            // 格式正确
            log.info("检查token是否合法、过期");
            String authorization = authorizationList.get(0).split(" ")[1];

            Map<String, ?> checkToken = authService.checkToken(authorization);
            log.info("校验结果：" + checkToken);

            // 判断结果
            log.info("判断状态码");
            Object code = checkToken.get("code");
            if (code != null && (Integer) checkToken.get("code") == 701){
                log.info("token过期!");
                log.info("刷新token");
                refreshToken(request, response);
            }else if (code != null && (Integer) checkToken.get("code") == 702){
                log.info("token无效，请重新登录");
                return accessDenied(exchange,403,"请登录");
            }else {
                log.info("token合法");
            }
        }

        // 放行
        return chain.filter(exchange);
    }

    /**
     * 对token、refreshtoken进行基础校验
     * @param exchange
     * @return
     */
    private boolean checkTokenBaseInfo(ServerWebExchange exchange){
        ServerHttpRequest request = exchange.getRequest();
        log.info("获取token和refreshtoken");
        List<String> refreshList = request.getHeaders().get("RefreshToken");
        List<String> authorizationList = request.getHeaders().get("Authorization");

        log.info("判断是否有token、refresh token，Redis中是否有refresh token");
        if (refreshList.size() == 0){
            log.info("没有refresh token");
            return false;
        }
        if (!redisTemplate.hasKey(refreshList.get(0))){
            log.info("refresh token过期或无效");
            return false;
        }
        if (authorizationList.size() == 0){
            log.info("没有token");
            return false;
        }
        if (!authorizationList.get(0).startsWith("Bearer")){
            log.info(authorizationList.get(0));
            log.info("token格式不对");
            return false;
        }
        return true;
    }

    /**
     * 利用webclient发送请求刷新token
     * @param request
     * @param response
     */
    private void refreshToken(ServerHttpRequest request, ServerHttpResponse response) {
        log.info("获取refresh_token");
        String refresh = request.getHeaders().get("Refreshtoken").get(0);

        log.info("从redis中获取客户端信息");
        Object clientId = redisTemplate.opsForHash().get(refresh, "client_id");
        Object clientSecret = redisTemplate.opsForHash().get(refresh, "client_secret");
        log.info("获取到客户端信息：" + clientId + "," + clientSecret);
        String is = clientId + ":" + clientSecret;

        FutureTask<RefreshResponse> futureTask = new FutureTask<>(new Callable<RefreshResponse>() {
            @Override
            public RefreshResponse call() throws Exception {
                RefreshResponse block = webClient.post()
                        .uri("/oauth/token?grant_type=refresh_token&refresh_token=" + refresh)
                        .header("Authorization", "Basic " + Base64.getEncoder().encodeToString(is.getBytes()))
                        .retrieve()
                        .bodyToMono(RefreshResponse.class)
                        .block();
                return block;
            }
        });

        // 开启线程发送请求
        new Thread(futureTask).start();

        //获取数据
        try {
            RefreshResponse result = futureTask.get();
            log.info("刷新结果：" + result);
            log.info("将新的token设置到请求头中");
            request.mutate().header("Authorization", "Bearer " + result.getAccess_token()).build();

            log.info("将新的token、refresh token设置到响应头返回给前端");
            response.getHeaders().add("access_token", result.getAccess_token());
            response.getHeaders().add("refresh_token", result.getRefresh_token());
            // 暴露头
            response.getHeaders().add("Access-Control-Expose-Headers","access_token,refresh_token");

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    // 拒绝访问
    private static Mono<Void> accessDenied(ServerWebExchange exchange,int code, String message) {
        //获取response对象
        ServerHttpResponse response = exchange.getResponse();

        //准备数据
        ResponseResult<Object> result = new ResponseResult<>(code,message,null);
        //将数据转换成byte数组
        byte[] data = null;
        try {
            data = new ObjectMapper().writeValueAsString(result).getBytes(StandardCharsets.UTF_8);
        }catch (Exception e){
            e.printStackTrace();
        }
        //创建数据缓存对象
        DataBuffer buffer = response.bufferFactory().wrap(data);
        //设置响应头
        response.getHeaders().add("Content-Type","application/json;charset=utf-8");
        //返回数据
        return response.writeWith(Mono.just(buffer));
    }

    /**
     * 判断请求是否需要登录
     * @param path
     * @return
     */
    private boolean requireAuthentication(String path){
        // 不需要登录的
        List<String> precise = Arrays.asList("/oauth/token");
        //List<String> like = Arrays.asList("");
        // 原子类型
        AtomicBoolean noAuth = new AtomicBoolean(true);

        precise.forEach(uri ->{
            if (path.equals(uri)){
                noAuth.set(false);
                return;
            }
        });
//        like.forEach(prefix ->{
//            if (path.startsWith(prefix)){
//                noAuth.set(false);
//                return;
//            }
//        });

        return noAuth.get();
    }

    @Override
    public int getOrder() {
        return 0;
    }
}